//@author A0101119Y
#include "GAccountsRequestAgent.h"

using namespace Poco::Net;
using namespace Poco;
using namespace Twentyfifth;
using namespace std;


const string GAccountsRequestAgent::GACCOUNTS_HOSTNAME = "https://accounts.google.com";
const string GAccountsRequestAgent::GACCOUNTS_PATH_AUTH = "/o/oauth2/device/code";
const string GAccountsRequestAgent::GACCOUNTS_PATH_TOKEN = "/o/oauth2/token";
const string GAccountsRequestAgent::LABEL_CLIENT_ID = "client_id";
const string GAccountsRequestAgent::LABEL_CLIENT_SECRET = "client_secret";
const string GAccountsRequestAgent::LABEL_SCOPE = "scope";
const string GAccountsRequestAgent::LABEL_DEVICE_CODE = "device_code";
const string GAccountsRequestAgent::LABEL_USER_CODE = "user_code";
const string GAccountsRequestAgent::LABEL_CODE = "code";
const string GAccountsRequestAgent::LABEL_VERIFICATION_URL = "verification_url";
const string GAccountsRequestAgent::LABEL_EXPIRES_IN = "expires_in";
const string GAccountsRequestAgent::LABEL_INTERVAL = "interval";
const string GAccountsRequestAgent::LABEL_TOKEN_TYPE = "token_type";
const string GAccountsRequestAgent::LABEL_REFRESH_TOKEN = "refresh_token";
const string GAccountsRequestAgent::LABEL_ACCESS_TOKEN = "access_token";
const string GAccountsRequestAgent::LABEL_GRANT_TYPE = "grant_type";
const string GAccountsRequestAgent::LABEL_SEPARATOR = "=";
const string GAccountsRequestAgent::LABEL_AND = "&";
const string GAccountsRequestAgent::LABEL_ERROR = "error";
const string GAccountsRequestAgent::VALUE_ACCESS_DENIED = "access_denied";
const string GAccountsRequestAgent::VALUE_JSON = "application/json";
const string GAccountsRequestAgent::VALUE_FORM_URLENCODED = "application/x-www-form-urlencoded";
const string GAccountsRequestAgent::GACCOUNTS_SCOPE = "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.readonly";
const string GAccountsRequestAgent::GACCOUNTS_GRANT_TYPE = "http://oauth.net/grant_type/device/1.0";
const string GAccountsRequestAgent::MESSAGE_NAVIGATE_TO_URL = "Please go to %s to continue with the login process.\nEnter the following access code: %s\nWaiting for response...";
const string GAccountsRequestAgent::MESSAGE_EXCEPTION_REFRESH = "Access token cannot be renewed. Reason: ";
const string GAccountsRequestAgent::MESSAGE_EXCEPTION_ACCESS_DENIED = "Login failed. Access has been denied.";
const string GAccountsRequestAgent::DUMMY_STRING = "%s";
const int GAccountsRequestAgent::LIMIT_TIMEOUT_SECONDS = 300;

GAccountsRequestAgent::GAccountsRequestAgent() {
	string client_id = variablemgr.getValue(LABEL_CLIENT_ID);
	string client_secret = variablemgr.getValue(LABEL_CLIENT_SECRET);
	clientId = client_id;
	clientSecret = client_secret;
}

void GAccountsRequestAgent::setClientId(string id) {
	clientId = id;
}

void GAccountsRequestAgent::setClientSecret(string secret) {
	clientSecret = secret;
}

Status GAccountsRequestAgent::firstAuthenticate() {
	try {
		stringstream requestss;
		requestss << LABEL_CLIENT_ID << LABEL_SEPARATOR << clientId;
		requestss << LABEL_AND;
		requestss << LABEL_SCOPE << LABEL_SEPARATOR << GACCOUNTS_SCOPE;
		string fullURI = GACCOUNTS_HOSTNAME + GACCOUNTS_PATH_AUTH;
		setURI(fullURI);
		initSession();
		request.setContentType(VALUE_FORM_URLENCODED);
		setRequestContent(requestss.str());
		setRequestMethod(METHOD_POST);
		sendRequest();
		map<string, string> firstResponse = parser.parseJsonToMap(responseContent);
		string deviceCode = firstResponse.at(LABEL_DEVICE_CODE);
		string userCode = firstResponse.at(LABEL_USER_CODE);
		string verificationUrl = firstResponse.at(LABEL_VERIFICATION_URL);
		int interval = stoi(firstResponse.at(LABEL_INTERVAL));
		string msg = MESSAGE_NAVIGATE_TO_URL;
		msg.replace(msg.find(DUMMY_STRING), DUMMY_STRING.length(), verificationUrl);
		msg.replace(msg.find(DUMMY_STRING), DUMMY_STRING.length(), userCode);
		cout << msg;
		firstTokenRetrieval(deviceCode, interval);
		variablemgr.setValue(LABEL_REFRESH_TOKEN, refreshToken);
		variablemgr.setValue(LABEL_ACCESS_TOKEN, accessToken);
		DateTime expiryDt;
		expiryDt.setToNow();
		expiryDt.offset(expiresIn);
		variablemgr.setValue(LABEL_EXPIRES_IN, parser.parseDtTaskToGoogle(expiryDt));
		variablemgr.writeMapToFile();
	}
	catch (Exception &e) {
		return Status(Status::ERR_OTHER, e.displayText());
	}
	catch (exception&) {
		return Status(Status::ERR_OTHER);
	}
	return Status(Status::OK);
}

void GAccountsRequestAgent::firstTokenRetrieval(string deviceCode, int interval) {
	stringstream requestss;
	requestss << LABEL_CLIENT_ID << LABEL_SEPARATOR << clientId;
	requestss << LABEL_AND;
	requestss << LABEL_CLIENT_SECRET << LABEL_SEPARATOR << clientSecret;
	requestss << LABEL_AND;
	requestss << LABEL_CODE << LABEL_SEPARATOR << deviceCode;
	requestss << LABEL_AND;
	requestss << LABEL_GRANT_TYPE << LABEL_SEPARATOR << GACCOUNTS_GRANT_TYPE;
	int repeats = LIMIT_TIMEOUT_SECONDS / interval;

	int msInterval = interval * 1000;
	string fullUri = GACCOUNTS_HOSTNAME + GACCOUNTS_PATH_TOKEN;
	setURI(fullUri);
	setRequestContent(requestss.str());
	setRequestMethod(METHOD_POST);
	request.setContentType(VALUE_FORM_URLENCODED);
	for (int i = 0; i < repeats; i++) {
		sendRequest();
		map<string, string> authResponse = parser.parseJsonToMap(responseContent);
		try {
			tokenType = authResponse.at(LABEL_TOKEN_TYPE);
			accessToken = authResponse.at(LABEL_ACCESS_TOKEN);
			refreshToken = authResponse.at(LABEL_REFRESH_TOKEN);
			expiresIn = stoi(authResponse.at(LABEL_EXPIRES_IN));
			cout << endl;
			return;
		}
		catch (exception) {
			string errorReason = authResponse.at(LABEL_ERROR);
			if (errorReason == VALUE_ACCESS_DENIED) {
				throw Poco::NoPermissionException(MESSAGE_EXCEPTION_ACCESS_DENIED);
			}
		} // intentionally left blank to indicate invalid response, and to try again.
		Sleep(msInterval);
		cout << ".";
	}
	throw Poco::TimeoutException();
}

void GAccountsRequestAgent::refreshAccessToken() {
	refreshToken = variablemgr.getValue(LABEL_REFRESH_TOKEN);
	stringstream requestss;
	requestss << LABEL_CLIENT_ID << LABEL_SEPARATOR << clientId;
	requestss << LABEL_AND;
	requestss << LABEL_CLIENT_SECRET << LABEL_SEPARATOR << clientSecret;
	requestss << LABEL_AND;
	requestss << LABEL_REFRESH_TOKEN << LABEL_SEPARATOR << refreshToken;
	requestss << LABEL_AND;
	requestss << LABEL_GRANT_TYPE << LABEL_SEPARATOR << LABEL_REFRESH_TOKEN;
	string fullUri = GACCOUNTS_HOSTNAME + GACCOUNTS_PATH_TOKEN;
	setURI(fullUri);
	initSession();
	request.setContentType(VALUE_FORM_URLENCODED);
	setRequestContent(requestss.str());
	setRequestMethod(METHOD_POST);
	sendRequest();
	try {
		map<string, string> authResponse = parser.parseJsonToMap(responseContent);
		accessToken = authResponse.at(LABEL_ACCESS_TOKEN);
		expiresIn = stoi(authResponse.at(LABEL_EXPIRES_IN));
		variablemgr.setValue(LABEL_ACCESS_TOKEN, accessToken);
		DateTime dt;
		dt.setToNow();
		dt.offset(expiresIn);
		variablemgr.setValue(LABEL_EXPIRES_IN, parser.parseDtTaskToGoogle(dt));
	}
	catch (Exception &e) {
		if (responseCode == 401) {
			cerr << MESSAGE_EXCEPTION_REFRESH << responseReason << endl;
			throw Poco::InvalidAccessException(401);
		} else {
			cerr << MESSAGE_EXCEPTION_REFRESH << e.displayText() << endl;
		}
	}
}

